package edu.syr.iis.web.model;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Transient;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


@Entity
public class Conversation {

	public static final int HIGH_PERCENTILE = 90;
	public static final int MID_PERCENTILE = 70;
		
	public final static int AGREE_FUNCTION = 1;
	public final static int AGREE_PART_DOMAIN = 2;
	public final static int AGREE_FULL_DOMAIN = 4;
	
	public final static int AGREE_FUNCTION_PART_DOMAIN = 8;
	public final static int AGREE_FUNCTION_FULL_DOMAIN = 16;
	
	private static Log log = LogFactory.getLog(Conversation.class);
	
    int conversationId;
    Dataset dataset;
    
    int month;
    int year;
    int recordId;
    int institutionId;
    int questionId;
    
    String dateTimeEntered;
    String referredBy;
    String assignedTo;
    String waitTime;
    String sessionTime;
    
    ConversationLabel conversationLabel;
    
    String note;
    
    int numMessages;
    int numType1Messages;
    int numMessageTurns;
        
    List <Message> messages;
    Set <Assignment> assignments;
    
    /*
     * public double getAverageWindowDiff(User u1, User u2)
     * 
     * Calculate the WindowDiff for each messages and return the average
     *  
     */
    @Transient
    public double getAverageWindowDiff(User u1, User u2)
    {
    	double sumWindowDiffs = 0.0;
    	
    	System.out.println("messages: " + getMessages().size());
    	
    	for (Message message: getMessages())
    	{  
    		double wd = message.getWindowDiff(u1, u2);
    		
    		System.out.println("Message " + message.getMessageId() + "'s wd: " + wd);
    		sumWindowDiffs += wd;
    	}
    	
    	System.out.println("sumWindowDiff: " + sumWindowDiffs);
    	
    	return sumWindowDiffs / getMessages().size();    	
    }
    
    @Transient
    public double getWindowDiff(User u1, User u2)
    {
		int nTotal = 0;           // Number of words in the conversation 
		int matched = 0;	  // Number of boundaries matched	

		int numSegments1 = 0;
		int numSegments2 = 0;
		int totalN = 0;
		
		for (Message message: messages)
		{
			int n = message.content.split("(\\s+)").length;

			numSegments1 += (message.getNumberOfBoundaries(u1, 0, n - 1) - 1);
			numSegments2 += (message.getNumberOfBoundaries(u2, 0, n - 1) - 1);
		
			totalN += n;
		}
		int k =  (int) ((double) totalN / numSegments1 + (double) totalN / numSegments2) / 2;		
				
		for (Message message: messages)
		{
			int n  = message.content.split("(\\s+)").length;
			nTotal += n;
			
			for (int i = 0; i < n - k; i++)		
			{
				int b1 = message.getNumberOfBoundaries(u1, i, i + k);
				int b2 = message.getNumberOfBoundaries(u2, i, i + k);
			
				if (b1 != b2)
					matched++;			
			}			
		}
		
		return (1.0 / (nTotal - k)) * (double) matched;		
    	
    }
    
    
    
    @Transient
    public int getNumberOfTextSegmentsBy(User user)
    {
    	return 0;
    	
    }
    
    
    @Transient
    public DisagreementMatrix getDisagreementMatrix(User u1, User u2, int level)
	{
    	ArrayList<Conversation> conversations = new ArrayList<Conversation>();
    	conversations.add(this);
    	return new DisagreementMatrix(u1, u2, conversations, level);
	}
    
    @Transient
	public String getAverageAgreementString()
	{
		Set<User> annotators = getAnnotators();
		String agreement = String.format("Overall: %.2f, ", getAverageAgreement());
		
		for (User annotator: annotators)
			agreement += String.format("%s: %.2f, ", annotator.getUserName(), getAverageAgreement(annotator));

		agreement = agreement.replaceFirst(",\\s$", "");
		return agreement;
	}
	
    @Transient 
    public String getAverageFunctionAgreementString(User user)
    {
    	Set <User> annotators = getAnnotators();
    	if (! annotators.contains(user))
    		return "Not assigned";
    	else if (annotators.size() < 2)
    		return "NA";
    	
    	return toPercentileSpanString(getAverageFunctionAgreement(user));
   }
    
    
    @Transient 
    public String getAverageAgreementString(User user)
    {
    	Set <User> annotators = getAnnotators();
    	if (! annotators.contains(user))
    		return "Not assigned";
    	else if (annotators.size() < 2)
    		return "NA";
    	
    	return toPercentileSpanString(getAverageAgreement(user));
   }
    
    @Transient 
    public String getMessageWiseAgreementString(User user)
    {
    	Set <User> annotators = getAnnotators();
    	if (! annotators.contains(user))
    		return "Not assigned.";
    	else if (annotators.size() < 2)
    		return "NA";
    
    	return toPercentileSpanString(getMessageWiseAgreement(user));
    }

    String toPercentileSpanString(double agreement)
    {
    	if (Double.isNaN(agreement))
    		return "<span class='nan_percentile'>NaN</font>";

    	int percentile = (int) (agreement * 100); 
    	if (percentile > HIGH_PERCENTILE)
    		return "<span class='high_percentile'>" + percentile + "%</font>";
    	else if (percentile > MID_PERCENTILE )
    		return "<span class='mid_percentile'>" + percentile + "%</font>";
    	else
    		return "<span class='low_percentile'>" + percentile + "%</font>";	
    }

    String toPercentileSpanString0(double agreement)
    {
    	if (Double.isNaN(agreement))
    		return "<font coor=\"black\">NaN</font>";

    	int percentile = (int) (agreement * 100); 
    	if (percentile > HIGH_PERCENTILE)
    		return "<font color=\"green\">" + percentile + "%</font>";
    	else if (percentile > MID_PERCENTILE )
    		return "<font color=\"blue\">" + percentile + "%</font>";
    	else
    		return "<font color=\"red\">" + percentile + "%</font>";	
    }
    
    
    @Transient
	public double getAverageFunctionAgreement(User user)
	{
		double sum = 0.0;
		int count = 0;
		
		for (Message message: messages)
		{
			if (message.getDialogueActs(user).size() != 0)
			{
				sum += message.getFunctionAgreement(user);
				count++;
			}
		}
		// If the annotator hasn't annotated any messages, it will return NaN
		//
		return sum / count;
	}
    
    
    @Transient
	public double getMessageWiseAgreement(User user)
	{
		double sum = 0.0;
		int count = 0;
		
		for (Message message: messages)
		{
			if (message.getDialogueActs(user).size() != 0)
			{
				sum += message.getMessageWiseAgreement(user);
				count++;
			}
		}
		// If the annotator hasn't annotated any messages, it will return NaN
		//
		return sum / count;
	}
    
    
    @Transient
	public double getAverageAgreement(User user)
	{
		double sum = 0.0;
		int count = 0;
		
		for (Message message: messages)
		{
			if (message.getDialogueActs(user).size() != 0)
			{
				sum += message.getAgreement(user);
				count++;
			}
		}
		// If the annotator hasn't annotated any messages, it will return NaN
		//
		return sum / count;
	}
	
    @Transient
	public double getAverageAgreement()
	{
		Set<User> annotators = getAnnotators();
		double sum = 0.0;
		int count = 0;  // not using annotators.size(), because there are annotators that haven't done the job. 
		
		for (User annotator: annotators)
		{
				double agreement = getAverageAgreement(annotator);
				if (!Double.isNaN(agreement)) 
				{
					sum += agreement; 
					count++;
				}
		}
		return sum / count;
	}
	
	@Transient
    public Message getMessageBySequence(int sequence) {
    	for (Message message: messages)
    	{
    		if (message.sequence == sequence)
    			return message;
    	}
    	return null;
    }
    
	
    @Transient
    public SortedSet<DialogueAct> getSortedDialogueActs()
    {
    	log.debug("Entering getSortedDialogueActs() with message size: " + messages.size());
    	
    	SortedSet<DialogueAct> sortedDialogueActs = new TreeSet<DialogueAct>(new DialogueActComparator());
    	
    	for (Message message: messages)
    	{
    		for (DialogueAct dialogueAct: message.getDialogueActs())
    			sortedDialogueActs.add(dialogueAct);
    	}
    	
    	log.debug("Exiting getSortedDialogueActs() with sortedDialogueActs of size: " + sortedDialogueActs.size());
    	return sortedDialogueActs;
    }

    @Transient
    public Message getFirstMessage()
    {
    	return getMessageBySequence(1);
    }
    
    @Transient
    public Message getLastMessage()
    {
    	return getMessageBySequence(numMessages);
    }
     
    @Id
    public int getConversationId()
    {
        return conversationId;
    }
    
    @ManyToOne
    @JoinColumn(name="datasetId")
    public Dataset getDataset()
    {
        return dataset;
    }
    
    public void setDataset(Dataset dataset)
    {
        this.dataset = dataset;
    }
    
    @ManyToOne
    @JoinColumn(name="conversationLabelId")
    public ConversationLabel getConversationLabel()
    {
        return conversationLabel;
    }
    public void setConversationLabel(ConversationLabel conversationLabel)
    {
        this.conversationLabel = conversationLabel;
    }
    
	@OneToMany(mappedBy="conversation", targetEntity=Message.class, fetch=FetchType.EAGER, cascade=CascadeType.ALL)
    //@OneToMany
    // @JoinColumn(name="message_id")
	@OrderBy("sequence")
    public List<Message> getMessages() {
		return messages;
	}
	
	public void setMessages(List<Message> messages) {
		this.messages = messages;
	}

	@OneToMany(mappedBy="conversation", targetEntity=Assignment.class) 	
	public Set<Assignment> getAssignments() {
		return assignments;
	}	
	public void setAssignments(Set<Assignment> assignments) {
		this.assignments = assignments;
	}


	@Transient
    public Set<User> getAnnotators() {
		HashSet<User> annotators = new HashSet<User>();
		
		for (Assignment  assignment: getAssignments())
		{
			annotators.add(assignment.getUser());
		}
		return annotators;
	}

	public String toLogString() {
		 String logString = "ConversationId:     " + conversationId + "\n" +
			   "numMesssages:       " + numMessages + "\n" +
			   "num of messages:    " + messages.size() + "\n" +
			   "num of assignments: " + getAssignments().size() + "\n" + 
			   "num of annotators:  " + getAnnotators().size() + "\n" +
			   "annoators: \n";
		 
		 for (User annotator: getAnnotators())
			 logString += "             " + annotator.userName + "\n";
			 		
		return logString;       
	}
		
	@Transient
	public int getNumAnnotated(User user)
	{
		int count = 0;
		for (Message message: messages)
		{
			Set<DialogueAct> das = message.getDialogueActs(user);
			if (das.size() > 0)
				count++;			
		}
		return count;
	}
	
	public int getNumType1Messages()
    {
        return numType1Messages;
    }
	
    public void setNumMessages(int numMessages)
    {
        this.numMessages = numMessages;
    }
    
    public void setNumType1Messages(int numType1Messages)
    {
        this.numType1Messages = numType1Messages;
    }
    
    public String getNote()
    {
        return note;
    }
    
    public void setNote(String note)
    {
        this.note = note;
    }        	
	
    public int getMonth()
    {
        return month;
    }
    public int getYear()
    {
        return year;
    }
    public int getRecordId()
    {
        return recordId;
    }
    public int getInstitutionId()
    {
        return institutionId;
    }
    public int getQuestionId()
    {
        return questionId;
    }
    public String getDateTimeEntered()
    {
        return dateTimeEntered;
    }
    public String getReferredBy()
    {
        return referredBy;
    }
    public String getAssignedTo()
    {
        return assignedTo;
    }
    public String getWaitTime()
    {
        return waitTime;
    }
    public String getSessionTime()
    {
        return sessionTime;
    }
    public void setConversationId(int conversationId)
    {
        this.conversationId = conversationId;
    }

    public void setMonth(int month)
    {
        this.month = month;
    }
    public void setYear(int year)
    {
        this.year = year;
    }
    public void setRecordId(int recordId)
    {
        this.recordId = recordId;
    }
    public void setInstitutionId(int institutionId)
    {
        this.institutionId = institutionId;
    }
    public void setQuestionId(int questionId)
    {
        this.questionId = questionId;
    }
    public void setDateTimeEntered(String dateTimeEntered)
    {
        this.dateTimeEntered = dateTimeEntered;
    }
    public void setReferredBy(String referredBy)
    {
        this.referredBy = referredBy;
    }
    public void setAssignedTo(String assignedTo)
    {
        this.assignedTo = assignedTo;
    }
    public void setWaitTime(String waitTime)
    {
        this.waitTime = waitTime;
    }
    public void setSessionTime(String sessionTime)
    {
        this.sessionTime = sessionTime;
    }
    
    public int getNumMessages()
    {
    	return numMessages;
    }    
    

    public int getNumMessageTurns()
    {
        return numMessageTurns;
    }
    public void setNumMessageTurns(int numMessageTurns)
    {
        this.numMessageTurns = numMessageTurns;
    }
    

    

}